package calao.plan;

import java.util.HashMap;
import java.util.Vector;

import calao.DesktopServiceImpl;
import calao.plan.client.Resource;
import calao.plan.client.Task;
import calao.plan.client.WorkPeriod;
import calao.storage.EqualCondition;
import calao.storage.RangeCondition;
import calao.storage.Record;
import calao.storage.Search;
import calao.storage.Storage;
import calao.util.Cursor;
import static calao.util.Logger.*;

public class PlanServiceImpl 
extends DesktopServiceImpl
implements ExtendedPlanService {

	public Resource createResource(WorkPeriod[] workPeriods) 
	{
		for (WorkPeriod period : workPeriods) {
			if (!period.isValid()) throw new IllegalArgumentException();
		}
		Storage storage = getWorkspace().getStorage();
		Record record = new Record(ResourceDB.TABLE);
		record.set(ResourceDB.ID, storage.getNextValue(ResourceDB.ID_SEQUENCE));
		record.set(ResourceDB.WORK_PERIODS, workPeriods);
		storage.store(record);
		return ResourceDB.INFO_BINDING.toObject(record);
	}

	public Resource getResource(int rid) 
	{
		Record record = getWorkspace().getStorage().fetch(ResourceDB.TABLE, rid);
		return record!=null? ResourceDB.INFO_BINDING.toObject(record) : null;
	}

	public synchronized void modifyResource(Resource info) 
	{
		if (info.getWorkPeriods()==null || info.getWorkPeriods().length==0
				|| getWorkspace().getStorage().fetch(ResourceDB.TABLE, info.getId())==null)
		{
			throw new IllegalArgumentException();
		}
		for (WorkPeriod period : info.getWorkPeriods()) {
			if (!period.isValid()) throw new IllegalArgumentException();
		}
		getWorkspace().getStorage().store(ResourceDB.INFO_BINDING.toRecord(info));
		computePlan();
	}

	public Task[] saveTasks(Task[] info)
	{
		Storage storage = getWorkspace().getStorage();
		HashMap<Integer,Integer> idMap = new HashMap<Integer,Integer>();
		// set identifiers for new tasks
		for (Task task : info) {
			if (task.getId()<=0) {
				int id = storage.getNextValue(TaskDB.ID_SEQUENCE);
				if (task.getId()<0)	idMap.put(task.getId(), id);
				task.setId(id);
				TaskType type = TaskType.getTaskType(task);
				if (type==null) throw new IllegalArgumentException("Unknown task type");
				Record record = new Record(TaskDB.TABLE);
				record.set(TaskDB.ID, task.getId());
				record.set(TaskDB.TYPE, storage.getNumericId(type.binding));
				record.set(TaskDB.POOL, 0);
				record.set(TaskDB.RESOURCE, 0);
				storage.store(record);
			}
		}
		// update dependencies that point to temporary identifiers
		for (Task task : info) {
			int[] deps = task.getDependencies();
			if (deps!=null) for (int i=0; i<deps.length; i++) {
				if (deps[i]<0) {
					Integer id = idMap.get(deps[i]);
					if (id==null) throw new IllegalArgumentException();
					deps[i] = id;
				}
				else if (deps[i]==0) throw new IllegalArgumentException();
			}
		}
		// modify tasks
		return modifyTasks(info);
	}
	
	static Task getTaskId(Task[] source, int id)
	{
		for (Task t : source) if (t.getId()==id) return t;
		return null;
	}

	public Task[] getPeriodTasks(long periodStart, long periodEnd) 
	{
		return getPeriodTasks(periodStart, periodEnd, null);
	}

	public <T extends Task> 
	T[] getPeriodTasks(long periodStart, long periodEnd, TaskType<T> type) 
	{
		Storage storage = getWorkspace().getStorage();
		int startWeek = Weeks.getWeek(periodStart);
		int endWeek = Weeks.getWeek(periodEnd);
		log(DEBUG, "Fetching tasks from week %d to %d", startWeek, endWeek);
		Search search = new Search(TaskDB.TABLE);
		search.add(new RangeCondition(TaskDB.WEEK, startWeek, endWeek));
		if (type!=null) {
			int typeId = storage.getNumericId(type.binding);
			search.add(new EqualCondition(TaskDB.TYPE, typeId));
		}
		search.setDistinct(true);
		Vector<Task> result = new Vector<Task>();
		Cursor<Record> cursor = storage.listResults(search);
		while (cursor.next()) {
			Record task = cursor.get();
			if (task.getLong(TaskDB.START)<periodEnd 
					&& task.getLong(TaskDB.END)>periodStart)
			{
				result.add(getTaskInfo(task));
			}
		}
		cursor.close();
		return result.toArray(newTaskArray(result.size(), type));
	}
	
	@SuppressWarnings("unchecked")
	static <T extends Task> T[] newTaskArray(int length, TaskType<T> type)
	{
		if (type!=null)	return type.newTaskArray(length);
		else return (T[])new Task[length];
	}
	
	public <T extends Task> 
	T[] listScheduledResourceTasks(int resource, TaskType<T> type)
	{
		Storage db = getWorkspace().getStorage();
		Vector<Task> result = new Vector<Task>();
		for (int pool=SchedulePool.FIRST_POOL; pool<SchedulePool.LAST_POOL; pool++) {
			Search search = new Search(TaskDB.TABLE);
			search.add(new EqualCondition(TaskDB.RESOURCE, resource));
			search.add(new EqualCondition(TaskDB.POOL, pool));
			if (type!=null) {
				int typeId = db.getNumericId(type.binding);
				search.add(new EqualCondition(TaskDB.TYPE, typeId));
			}
			Cursor<Record> cursor = db.listResults(search);
			while (cursor.next()) {
				result.add(getTaskInfo(cursor.get()));
			}
			cursor.close();
		}
		return result.toArray(newTaskArray(result.size(), type));		
	}

	public <T extends Task> 
	T[] listUnscheduledTasks(TaskType<T> type)
	{
		Storage db = getWorkspace().getStorage();
		Vector<Task> result = new Vector<Task>();
		Search search = new Search(TaskDB.TABLE);
		search.add(new EqualCondition(TaskDB.POOL, SchedulePool.UNSCHEDULED));
		if (type!=null) {
			int typeId = db.getNumericId(type.binding);
			search.add(new EqualCondition(TaskDB.TYPE, typeId));
		}
		Cursor<Record> cursor = db.listResults(search);
		while (cursor.next()) {
			result.add(getTaskInfo(cursor.get()));
		}
		cursor.close();
		return result.toArray(newTaskArray(result.size(), type));		
	}
	
	public Task getTaskInfo(Record record)
	{
		Task info = (Task)record.get(TaskDB.TYPE_DATA);
		info.setId(record.get(TaskDB.ID));
		info.setDependencies(record.getAllInt(TaskDB.DEPENDENCIES));
		info.setDoer(record.getInt(TaskDB.RESOURCE));
		info.setPriority(record.getInt(TaskDB.PRIORITY));
		info.setStart((record.getLong(TaskDB.START)));
		info.setEnd(record.getLong(TaskDB.END));
		info.setWorkTime(record.getInt(TaskDB.WORK_TIME));
		info.setEndFixed(TaskDB.hasFlag(record, TaskDB.FIXED_END_FLAG));
		info.setStartFixed(TaskDB.hasFlag(record, TaskDB.FIXED_START_FLAG));
		return info;
	}

	@SuppressWarnings("unchecked")
	public <T extends Task> T[] getTasks(int[] tids, TaskType<T> type) 
	{
		T[] result = type.newTaskArray(tids.length);
		for (int i=0; i<tids.length; i++) {
			Storage storage = getWorkspace().getStorage();
			Record record = storage.fetch(TaskDB.TABLE, tids[i]);
			result[i] = record!=null? type.downcast(getTaskInfo(record)) : null;
		}
		return result;
	}

	public Task[] getTasks(int[] tids) 
	{
		Task[] result = new Task[tids.length];
		for (int i=0; i<tids.length; i++) {
			Storage storage = getWorkspace().getStorage();
			Record record = storage.fetch(TaskDB.TABLE, tids[i]);
			result[i] = record!=null? getTaskInfo(record) : null;
		}
		return result;
	}

	public synchronized Task[] modifyTasks(Task[] taskDetails) 
	{
		Storage storage = getWorkspace().getStorage();
		boolean planModified = false;
		for (Task info : taskDetails) {
			Record record = storage.fetch(TaskDB.TABLE, info.getId());
			record.set(TaskDB.TYPE_DATA, info);
			boolean tpmod = false;
			tpmod |= TaskDB.setResource(record, info.getDoer());
			tpmod |= TaskDB.setWorktime(record, info.getWorkTime());
			tpmod |= TaskDB.setPriority(record, info.getPriority());
			tpmod |= TaskDB.setDependencies(record, info.getDependencies());
			if (info.isStartFixed()) {
				if (info.getStart()<=0) throw new IllegalArgumentException();
				tpmod |= TaskDB.setFlag(record, TaskDB.FIXED_START_FLAG);
				tpmod |= TaskDB.setStart(record, info.getStart());
			}
			else if (TaskDB.hasFlag(record, TaskDB.FIXED_START_FLAG)) {
				tpmod |= TaskDB.clearFlag(record, TaskDB.FIXED_START_FLAG);
				tpmod |= TaskDB.setStart(record, 0);
			}
			if (info.isEndFixed()) {
				if (info.getEnd()<=0) throw new IllegalArgumentException();
				// fixed end requires fixed start
				if (!info.isStartFixed()) throw new IllegalArgumentException();
				tpmod |= TaskDB.setFlag(record, TaskDB.FIXED_END_FLAG);
				tpmod |= TaskDB.setEnd(record, info.getEnd());
			}
			else if (TaskDB.hasFlag(record, TaskDB.FIXED_END_FLAG)) {
				tpmod |= TaskDB.clearFlag(record, TaskDB.FIXED_END_FLAG);
				tpmod |= TaskDB.setEnd(record, 0);
			}
			if (tpmod) {
				if (TaskDB.hasFlag(record, TaskDB.FINALIZED_FLAG)) {
					throw new IllegalArgumentException("Cannot replan finalized task #"+info.getId());
				}
				planModified = true;
				// move to right pool
				int pool;
				if (info.isEndFixed()) pool = SchedulePool.FIXED;
				else {
					if (info.getDoer()==0 || info.getWorkTime()<=0) {
						pool = SchedulePool.UNSCHEDULED;
					}
					else if (info.isStartFixed()) {
						if (SchedulePool.getPool(record)!=SchedulePool.STARTED
								&& info.getStart()<System.currentTimeMillis()-Plan.FINALIZE_PERIOD)
						{
							throw new IllegalArgumentException();
						}
						pool = SchedulePool.STARTED;
					}
					else {
						pool = SchedulePool.SCHEDULED;
					}
				}
				SchedulePool.setPool(record, pool);
			}
			storage.store(record);
			log(DEBUG, "Stored task %s", record);
		}
		if (planModified) computePlan();
		return getTasks(getIds(taskDetails));
	}
	
	static int[] getIds(Task[] tasks)
	{
		int ids[] = new int[tasks.length];
		for (int i=0; i<ids.length; i++) {
			ids[i] = tasks[i].getId();
		}
		return ids;
	}

	synchronized void computePlan() 
	{
		Storage storage = getWorkspace().getStorage();
		Plan plan = new Plan(storage);
		plan.compute();
		plan.saveChanges();
	}

	public void deleteTask(int task) 
	{
		getWorkspace().getStorage().drop(TaskDB.TABLE, task);
		computePlan();
	}

}
